package cn.yrec.rain.analysis.program;

import cn.hutool.core.date.DateUtil;
import cn.yrec.rain.analysis.utils.DateUtils;
import cn.yrec.rain.analysis.utils.NumberUtils;
import cn.yrec.rain.analysis.vo.*;

import java.math.BigDecimal;
import java.util.*;

/**
 * 描述:
 *  相似性分析核心函数
 * @createDate: 2022/05/09 下午 7:31:29
 * @author: yanglichen
 */
public class CalculateFunction {

    /**
     * 方法描述: 寻找场次雨
     *
     * @author yanglichen
     * @date 2022-05-09 19:42
     **/
    public static void findRain(RainModel model) {
        List<RainfallResult> result = new ArrayList<>();
        Date requestStartDate = new Date(model.getStartDate().getTime());
        // 得到这些站码最早的时间
        Date pioneerDate = model.getPioneerDate();
        // 判断结束时间是否早于最早时间
        if (model.getEndDate().getTime() <= pioneerDate.getTime()) {
            model.setPrototypeRainfallList(result);
            return;
        }
        // 判断最早的时间和传入的开始时间那个早,如果传入的开始时间比最早的时间早就让开始时间为最早的时间
        if (requestStartDate.getTime() <= pioneerDate.getTime()) {
            requestStartDate.setTime(pioneerDate.getTime());
            model.getStartDate().setTime(pioneerDate.getTime());
        }
        // 在时间范围内寻找场次雨
        while (model.getStartDate().getTime() >= requestStartDate.getTime()){
            // 找到最早的一场相似雨
            RainfallResult rainfallResult = getRainfallResult(model);
            // 替换开始时间为找到的
            model.getEndDate().setTime(
                    rainfallResult.getRainfallDate().getTime()
                            - model.getRainfallParameters().getTimeInterval() * DateUtils.ONE_DAY_MILLI_SECOND
            );
            model.getStartDate().setTime(rainfallResult.getRainfallDate().getTime());
            result.add(rainfallResult);
            if (model.getStartDate().getTime() <= pioneerDate.getTime()) {
                break;
            }
        }
        model.setPrototypeRainfallList(result);
    }

    /**
     * 方法描述: 根据原型雨得到相似雨列表
     *
     * @param model 模型参数
     * @author yanglichen
     * @date 2022-05-09 21:33
     **/
    public static void findByRainfall(RainModel model) {
        //根据原型雨实例,寻找备选雨列表
        List<RainfallResult> resembleRainfallList = filterRainfallResultByTakeTimesAndRainfallQ(model);
        model.setResembleRainfallList(resembleRainfallList);
    }

    /**
     * 方法描述: 根据降雨历时和总降雨量寻找备选雨
     * (如果根据给定的条件找出的降雨场次数 < 20场次, 首先只增加历时范围(最大到3)
     * 如果历时增加到了3,备选雨场次数 < 20 ,增加总雨量的控制参数(每次0.05 最大增加到0.5)
     * 以上过程中只要当备选雨场次数 >= 20 ,则随时结束循环
     *
     * @param model 模型参数
     * @return 根据降雨相似度排序后的备选雨列表(只要前5条)
     * @author yanglichen
     * @date 2020-08-11 08:06
     **/
    private static List<RainfallResult> filterRainfallResultByTakeTimesAndRainfallQ(RainModel model) {
        // 得到原型雨
        RainfallResult prototypeRainfall = model.getPrototypeRainfall();
        // 最大雨量容差
        Double rainfallQTolerance = model.getRainfallParameters().getRainfallQTolerance();
        // 最大降雨间隔数据
        Integer timeInterval = model.getRainfallParameters().getTimeInterval();

        double startRainfallQTolerance = 0.00;
        //构造结果集
        Set<RainfallResult> resultSet = new HashSet<>();

        // 计算开始的时间
        long startTime = System.currentTimeMillis();
        while (true) {
            //判断小于20场中的跳出情况(历时范围达到3 并且 雨量容差达到 最大)
            if (timeInterval >= 3 && startRainfallQTolerance >= rainfallQTolerance) {
                break;
            }
            //  根据给定的降雨历时,降雨总雨量范围得到降雨列表
            List<RainfallResult> rainfallResults =
                    getRainfallListByRainfallTakeTimeAndRainfallQ(model, timeInterval, startRainfallQTolerance);
            //备选雨列表添加到Set中,用于去重数据
            resultSet.addAll(rainfallResults);

            //判断set中的数量的大小是否<20
            if (resultSet.size() < 20) {
                //判断历时范围是否<3
                if (timeInterval < 3) {
                    //对历时进行加一
                    timeInterval++;
                } else {
                    //对雨量控制参数+0.05
                    startRainfallQTolerance += 0.05;
                }
            } else {
                //如果数量已经达到了20就直接跳出
                break;
            }
            // 结束时间
            long endTime = System.currentTimeMillis();
            long currentTime = endTime - startTime;
            // 10分钟强制跳出
            if (currentTime > 1000 * 60 * 10) break;
        }
        /* 当程序运行到这里set中已经有了一定的数据
         * 此时数据有两种情况
         * 1.条件没跑完就已经找到了20条
         * 2.条件跑到头了,但是还没有20条,可能只有10条甚至更少
         * 即将对set中的所有数据进行相似值的计算,并输出到List中
         */
        List<RainfallResult> resultList = new ArrayList<>(resultSet);
        //根据原型雨对所有备选雨计算相似度
        List<RainfallResult> rainfallResults = sortRainfallResultListByResemblance(model, resultList, prototypeRainfall);
        //得到最低的相似度(最后的哪一个就是相似度最低的)
        if (rainfallResults.size() <= 1) {
            return rainfallResults;
        }
        double minLookLike = rainfallResults.get(rainfallResults.size() - 1).getResemblance();
        //遍历计算相似度
        for (RainfallResult rainfallResult : rainfallResults) {
            double resemblance = NumberUtils.round(BigDecimal.valueOf(rainfallResult.getResemblance()), 4).doubleValue();
            double lookLikePoint = NumberUtils.round(BigDecimal.valueOf((1 - (resemblance / minLookLike)) * 100), 4).doubleValue();
            double rainfallQPoint = NumberUtils.round(BigDecimal.valueOf(1 - prototypeRainfall.getTotalRainfallQ() / rainfallResult.getTotalRainfallQ()), 4).doubleValue();

            System.err.println("相似值为: " + resemblance + " 相似性为: " + lookLikePoint + " 雨量容差为 : " + Math.abs(rainfallQPoint));
            //赋值相似度
            rainfallResult.setLookLikePoint(lookLikePoint);
        }
        //只要前10场次
        List<RainfallResult> finalResult = new ArrayList<>();
        //判断总场次,如果不足10场次直接返回
        if (rainfallResults.size() <= 10) {
            return rainfallResults;
        }
        //比10场次多就要前10场次
        for (int i = 0; i < 10; i++) {
            finalResult.add(rainfallResults.get(i));
        }
        return finalResult;
    }

    /**
     * 方法描述: 根据原型雨计算相似度对备选雨结果进行排序
     *
     * @param rainfallResultList      备选雨列表
     * @param prototypeRainfallResult 原型雨
     * @return 排序后的RainfallResult
     * @author yanglichen
     * @date 2020-07-28 17:19
     **/
    private static List<RainfallResult> sortRainfallResultListByResemblance(
            RainModel model, List<RainfallResult> rainfallResultList, RainfallResult prototypeRainfallResult) {
        //得到站码列表
        List<String> stcdList = prototypeRainfallResult.getRainfallParameters().getStcdList();
        //得到结果大集合
        Map<String, Map<Date, RaPDayRtVo>> allInformationMap = model.getAllInformationMap();
        //得到原型雨对应的站码和雨量的Map
        Map<String, Double> prototypeMap =
                getResemblanceMap(prototypeRainfallResult, allInformationMap);
        //得到新权重
        Map<String, Double> newWeight = getNewWeiight(allInformationMap);
        //遍历备选雨实例,计算相似值
        for (RainfallResult rainfallResult : rainfallResultList) {
            //得到备选雨对应的站码和雨量的Map
            Map<String, Double> otherMap = getResemblanceMap(rainfallResult, allInformationMap);
            //得到相似值
            Double resemblance = getResemblace(prototypeMap, otherMap, newWeight, stcdList);
            //封装相似值
            rainfallResult.setResemblance(resemblance);
        }
        //返回排序后的结果
        return sortRainfallResultsByResemblace(rainfallResultList);
    }

    /**
     * 方法描述: 根据备选雨的相似值对备选雨进行排序
     * (降序排序)
     *
     * @param rainfallResultList 备选雨列表
     * @return 排序后的备选雨的列表
     * @author yanglichen
     * @date 2020-07-28 17:38
     **/
    private static List<RainfallResult> sortRainfallResultsByResemblace(
            List<RainfallResult> rainfallResultList) {

        rainfallResultList.sort(Comparator.comparing(RainfallResult::getResemblance));
        return rainfallResultList;
    }

    /**
     * 方法描述: 根据传入的原型雨和备选雨的雨量站的降雨映射,权重映射,
     * 计算降雨的相似性
     *
     * @param prototypeMap 原型雨雨量映射
     * @param otherMap     备选雨雨量映射
     * @param weightMap    权重的映射
     * @param stcdList     站码列表
     * @return 降雨的相似性
     * @author yanglichen
     * @date 2020-07-28 17:03
     **/
    private static Double getResemblace(
            Map<String, Double> prototypeMap, Map<String, Double> otherMap,
            Map<String, Double> weightMap, List<String> stcdList) {
        /*
            新计算出的权重 * (累加原型雨中某站码在历时内的雨量 - 累加备选雨中某站码在历时中的雨量)²
            累加上面的结果后
            对结果开根号
        */
        //构造用于存放单个雨量站计算结果的结果集
        List<Double> reusltList = new ArrayList<>();
        //遍历站码列表得到不同的数据
        for (String stcd : stcdList) {
            //原型雨站码的降雨量
            Double prototypeQ = prototypeMap.get(stcd);
            //备选雨站码的降雨量
            Double otherQ = otherMap.get(stcd);
            //站码对应的权重
            Double stcdWeight = weightMap.get(stcd);
            double tempResult = 0D;
            if (!(prototypeQ == null || otherQ == null)) {
                //System.err.println("站码 "+stcd+" 原型雨降雨量: "+ prototypeQ+ " 备选雨降雨量: "+ otherQ);
                //执行计算 --> 权重 * (累加原型雨中某站码在历时内的雨量 - 累加备选雨中某站码在历时中的雨量)²
                tempResult = stcdWeight * Math.pow((prototypeQ - otherQ), 2);
            }
            reusltList.add(tempResult);
        }
        //对雨量站结果进行累加后求和
        double sumTempResult = 0D;
        for (Double cal : reusltList) {
            sumTempResult += cal;
        }
        return Math.sqrt(sumTempResult);
    }

    /**
     * 方法描述: 根据某一场降雨得到在该场次降雨中,
     * 每个站码所记录的降雨量的总和,并组成一个Map
     *
     * @param rainfallResult    某一场雨的降雨特征值信息
     * @param allInformationMap 包含所有信息的Map结果集
     * @return 在该场次降雨中, 每个站码记录的降雨量的总和, 并放入一个Map中
     * 例如: {站码1 = 22.0, 站码2 = 25.0, 站码3 = 28.8, ....}
     * @author yanglichen
     * @date 2020-07-28 16:40
     **/
    private static Map<String, Double> getResemblanceMap(
            RainfallResult rainfallResult, Map<String, Map<Date, RaPDayRtVo>> allInformationMap) {

        //从降雨特征值中得到相关信息
        //降雨起始日
        Date startDate = rainfallResult.getRainfallDate();
        //降雨截止日
        Date endDate = rainfallResult.getEndRainfallDate();
        //得到降雨的时间轴
        List<Date> dateList = DateUtils.getDateListByStartDateAndEndDate(startDate, endDate);
        //根据以上信息得到按照站码分类的List<List>
        List<List<RaPDayRtVo>> raPDayRtVosList = getRaPDayRtVosListByStcdList(allInformationMap, dateList);
        //得到并返回站码和历时内对应的降雨信息
        return new HashMap<>(getStcdRainfallQMap(raPDayRtVosList));
    }

    /**
     * 方法描述: 根据按照站码分类的List<RaPDayRtVo>和站码列表
     * 得到一个站码和站码内对应总雨量的Map
     *
     * @param raPDayRtVosList 按照站码分类的List<RaPDayRtVo>
     * @return 站码和站码内对应总雨量的Map
     * 例如: {站码1 = 22.0, 站码2 = 25.0, 站码3 = 28.8, ....}
     * @author yanglichen
     * @date 2020-07-28 16:52
     **/
    private static Map<String, Double> getStcdRainfallQMap(List<List<RaPDayRtVo>> raPDayRtVosList) {
        //构造结果集
        Map<String, Double> resultMap = new HashMap<>();
        for (List<RaPDayRtVo> raPDayRtVoList : raPDayRtVosList) {
            if (raPDayRtVoList.size() != 0) {
                //得到站码
                String stcd = raPDayRtVoList.get(0).getStcd();
                //初始化累加雨量和
                double sumQ = 0D;
                //遍历二级列表求和
                for (RaPDayRtVo raPDayRtVo : raPDayRtVoList) {
                    sumQ += raPDayRtVo.getRf();
                }
                //讲结果存入Map中
                resultMap.put(stcd, sumQ);
            }
        }
        return resultMap;
    }

    /**
     * 方法描述: 根据给定的降雨历时,降雨总雨量范围得到降雨列表
     *
     * @param rainfallTolerance  历时范围
     * @param rainfallQTolerance 降雨量容差
     * @return 备选雨列表
     * @author yanglichen
     * @date 2020-08-11 08:23
     **/
    private static List<RainfallResult> getRainfallListByRainfallTakeTimeAndRainfallQ(
            RainModel model, int rainfallTolerance, double rainfallQTolerance) {
        // 得到原型雨
        RainfallResult prototypeRainfall = model.getPrototypeRainfall();
        //得到历时和降雨日期对应的Map
        Map<Integer, List<Date>> rainTakeDaysAndDateListMap = model.getRainTakeDaysAndDateListMap();
        //原型雨的降雨降雨参数
        RainfallParameters parameter = prototypeRainfall.getRainfallParameters();
        //得到原型雨的历时
        int prototypeRainfallTakeTimes = prototypeRainfall.getRainfallTakeTime();
        //根据给定的降雨历时范围和原型雨的历时构造一个数组
        int[] rainfallTakeTimeArray = getRainfallTakeTimesToleranceArray(prototypeRainfallTakeTimes, rainfallTolerance);
        List<RainfallResult> takeTimesResult = new ArrayList<>();
        //根据给定的降雨历时范围和余量容差,直接进行过滤
        for (int takeTime : rainfallTakeTimeArray) {
            //根据历时得到降雨开始日期的列表
            List<Date> rainfallStartDateList = rainTakeDaysAndDateListMap.get(takeTime);
            if (rainfallStartDateList != null) {
                //遍历日期列表
                for (Date rainfallStartDate : rainfallStartDateList) {
                    //构造备选雨实例
                    RainfallResult rainfallResult = new RainfallResult();
                    //封装降雨参数
                    rainfallResult.setRainfallParameters(parameter);
                    //封装降雨起始日
                    rainfallResult.setRainfallDate(rainfallStartDate);
                    //封装降雨历时
                    rainfallResult.setRainfallTakeTime(takeTime);
                    //根据降雨起始日和历时得到降雨截止日并封装
                    Date endRainfallDate = DateUtils.getAfterDateByTimeZone(rainfallStartDate, takeTime);
                    rainfallResult.setEndRainfallDate(endRainfallDate);

                    //添加到历时的列表中
                    takeTimesResult.add(rainfallResult);
                }
            }
        }
        //为根据历时筛选出来的列表计算总降雨量
        List<RainfallResult> rainfallResults = calAllCulate(takeTimesResult, model);
        //得到备选雨的总降雨量
        double rainfallQ = prototypeRainfall.getTotalRainfallQ();
        //得到根据总雨量阈值得到的总雨量的最小值
        double rainfallMinQ = rainfallQ * (1 - rainfallQTolerance);
        //得到根据总雨量阈值得到的总雨量的最大值
        double rainfallMaxQ = rainfallQ * (1 + rainfallQTolerance);
        //构造最终结果集
        List<RainfallResult> finalRainfallResultList = new ArrayList<>();
        //遍历根据历时筛选出来的列表,将雨量不满足的备选雨进行过滤
        for (RainfallResult rainfallResult : rainfallResults) {
            //得到备选雨的降雨量
            double rainfallResultQ = rainfallResult.getTotalRainfallQ();
            //如果 备选雨雨量 >= 最小值 并且 备选雨雨量 <= 最大值
            if (rainfallResultQ >= rainfallMinQ && rainfallResultQ <= rainfallMaxQ) {
                rainfallResult.getRainfallParameters().setStcdList(parameter.getStcdList());
                finalRainfallResultList.add(rainfallResult);
            }
        }
        return finalRainfallResultList;
    }

    /**
     * 方法描述: 计算所有的备选雨的特征值
     *
     * @param rainfallResultList 备选雨列表
     * @return 备选雨列表
     * @author yanglichen
     * @date 2020-07-27 10:36
     **/
    private static List<RainfallResult> calAllCulate(List<RainfallResult> rainfallResultList, RainModel model) {
        //构造新的结果集
        List<RainfallResult> rainfallResults = new ArrayList<>();
        //遍历备选雨特征值计算结果列表
        for (RainfallResult rainfallResult : rainfallResultList) {
            //降雨结束的日期
            Date rainfallEndDate = rainfallResult.getEndRainfallDate();
            //更新参数
            model.getRainfallParameters().setCalDate(rainfallEndDate.getTime());
            model.setStartDate(rainfallResult.getRainfallDate());
            model.setEndDate(rainfallResult.getEndRainfallDate());
            //调用计算降雨特征值的方法计算特征值
            RainfallResult result = getRainfallResult(model);
            rainfallResults.add(result);
        }
        return rainfallResults;
    }

    /**
     * 方法描述: 根据降雨历时,时间容差范围,得到一个由这些历时容差范围组成的数组
     * 并且: 数组的下标随着范围的增大而增大
     *
     * @param rainfallTaketime  降雨历时
     * @param rainfallTolerance 降雨历时容差范围
     * @return 由这些历时容差范围组成的数组
     * 如: [5, 4, 6, 7, 9]
     * @author yanglichen
     * @date 2020-07-20 08:37
     **/
    private static int[] getRainfallTakeTimesToleranceArray(Integer rainfallTaketime, Integer rainfallTolerance) {
        //数组长度(1+容差*2)
        int tempTolerance = (rainfallTaketime * rainfallTolerance) / 10;
        if (tempTolerance < 1) {
            tempTolerance = 1;
        }
        int[] indexArray = new int[1 + tempTolerance * 2];
        //向数组中存放元素
        indexArray[0] = rainfallTaketime;
        for (int i = 1; i < indexArray.length; i++) {
            //偶数就添加增大的容差
            if (i % 2 == 0) {
                indexArray[i] = rainfallTaketime + (i / 2);
            } else {
                //奇数添加减少的容差
                indexArray[i] = rainfallTaketime - (i / 2) - 1;
            }
        }
        return indexArray;
    }


    /**
     * 方法描述: 得到降雨特征值结果
     *
     * @param model 模型封装数据对象
     * @return 降雨结果的封装类
     * @author yanglichen
     * @date 2020-07-24 17:22
     **/
    private static RainfallResult getRainfallResult(RainModel model) {
        // 得到降雨参数
        RainfallParameters rainfallParameters = model.getRainfallParameters();
        // 得到雨量站对应降雨天数的计数项的数据
        Map<Date, Integer> rainfallCountMap = model.getRainfallCountMap();
        //构造降雨特征值实体类
        RainfallResult rainfallResult = new RainfallResult();
        //得到开始计算的时间
        Date calDate = new Date(model.getEndDate().getTime());
        //降雨间隔
        Integer timeInterval = rainfallParameters.getTimeInterval();
        //---------------------------------------------上面是得到属性值,下面是开始计算数据----------------------------------------//
        //得到站码列表在各个时间点下的日雨量数据
        Map<String, Map<Date, RaPDayRtVo>> allInformationMap = model.getAllInformationMap();
        //计算降雨日期
        Date rainfallStartDate = findRainfallStartDate(rainfallCountMap, calDate, timeInterval);
        Date rainfallEndDate = findRainfallEndDate(rainfallCountMap, rainfallStartDate, timeInterval);

        //判断计算的时间是不是今天
        if (!(DateUtils.getNotHourDate(calDate).getTime() == DateUtils.getNotHourDate(new Date()).getTime())) {
            //不是今天 ,计算日期改成降雨结束日期
            calDate.setTime(rainfallEndDate.getTime());
        }
        //封装降雨结束的日期
        rainfallResult.setEndRainfallDate(rainfallEndDate);
        //初始化降雨历时
        Integer rainfallTakeTime = Math.toIntExact(DateUtil.betweenDay(calDate, rainfallStartDate, true));
        //得到用于计算每日笼罩面积的降雨阈值
        Double rainfallThresholdFoArea = rainfallParameters.getRainfallThresholdForArea();
        //封装降雨传入的参数
        rainfallResult.setRainfallParameters(rainfallParameters);
        //封装降雨日期
        rainfallResult.setRainfallDate(rainfallStartDate);
        //封装降雨历时
        rainfallResult.setRainfallTakeTime(rainfallTakeTime);
        //计算降雨总面积
        Double totalRainfallArea = getTotalRainfallArea(allInformationMap);
        //封装降雨总面积
        rainfallResult.setTotalRainfallArea(totalRainfallArea);
        //计算降雨总量
        Double totalRainfallQ = getTotalRainfallQ(allInformationMap, rainfallStartDate, rainfallEndDate);
        //封装降雨总量
        rainfallResult.setTotalRainfallQ(totalRainfallQ);
        //得到降雨期间的每日降雨笼罩面积的列表(单位:平方米)
        List<Double> dateRainfallArea =
                getTimesRainfallArea(allInformationMap, rainfallStartDate, rainfallEndDate, rainfallThresholdFoArea);
        //封装每日降雨笼罩面积
        rainfallResult.setDateRainfallArea(dateRainfallArea);
        //得到降雨期间的每日降雨量
        List<Double> dataRainfallQ = getTimesRainfallQ(allInformationMap, rainfallStartDate, rainfallEndDate);
        //封装降雨期间的每日降雨量
        rainfallResult.setDateRainfallQ(dataRainfallQ);
        //得到最大点雨量对象
        MaxRainObjectVo maxRainfall = getMaxRainfall(allInformationMap, rainfallStartDate, rainfallEndDate);
        // 封装最大点雨量
        rainfallResult.setMaxRainObject(maxRainfall);
        //得到前期雨量指数
        Double beforeRainfallPoint = getBeforeRainfallPoint(allInformationMap, rainfallStartDate);
        //封装前期雨量指数
        rainfallResult.setBeforeRainfallPoint(beforeRainfallPoint);
        // 滑动计算最大日期的降雨数据
        slideCalculateMaxRain(rainfallResult);
        return rainfallResult;
    }

    /**
     * 方法描述: 滑动计算最大降雨天数
     * @author yanglichen
     * @date 2023-07-06 11:25
     * @param rainfallResult 降雨对象
     **/
    private static void slideCalculateMaxRain(RainfallResult rainfallResult) {
        List<MaxRainDay> maxRainDayList = new ArrayList<>();
        // 降雨开始时间
        Date rainfallDate = rainfallResult.getRainfallDate();
        // 降雨结束时间
        Date endRainfallDate = rainfallResult.getEndRainfallDate();
        // 得到时段降雨过程对象
        List<Double> dateRainfallQ = rainfallResult.getDateRainfallQ();
        // 构造开始时间和结束时间相匹配的日期序列
        List<Date> dateList = DateUtils.getDateListByStartDateAndEndDate(rainfallDate, endRainfallDate);
        // 构造日期和降雨相对应的Map对象
        LinkedHashMap<Date, Double> dateRainMap = new LinkedHashMap<>();
        for (int i = 0; i < dateRainfallQ.size(); i++) {
            dateRainMap.put(dateList.get(i), dateRainfallQ.get(i));
        }
        // 判断时段降雨过程的数量
        for (int i = 0; i < dateRainMap.values().size(); i++) {
            if (i % 2 == 1) {
                MaxRainDay maxRainDayItem = new MaxRainDay();
                maxRainDayList.add(maxRainDayItem);
            }
        }
        // 滑动统计
        for (int i = 0; i < maxRainDayList.size(); i++) {
            MaxRainDay maxRainDay = maxRainDayList.get(i);
            Double sumRain = maxRainDay.getSumRain();
            int k = 2 * i + 1;
            for (int j = 0; j < dateList.size() - k; j++) {
                // 当前日期
                Date curDate = dateList.get(j);
                // 下一个日期
                Date nextDate = dateList.get(j + k);
                // 时段内的时间序列
                List<Date> tempDateList = DateUtils.getDateListByStartDateAndEndDate(curDate, nextDate);
                // 统计这段时间内的总降雨量
                Double tempSumRain = 0D;
                for (Date tempDate : tempDateList) {
                    Double tempRain = dateRainMap.get(tempDate);
                    tempSumRain += tempRain;
                }
                // 判断当前统计的最大雨量和已有的最大累计雨量的关系谁大谁小
                if (sumRain == null || sumRain < tempSumRain) {
                    sumRain = tempSumRain;
                    maxRainDay.setSumRain(tempSumRain);
                    maxRainDay.setStartDate(curDate);
                    maxRainDay.setEndDate(nextDate);
                    // 计算降雨时间间隔
                    int takeDays = (int) DateUtil.betweenDay(curDate, nextDate, true);
                    maxRainDay.setRainfallTakeTime(takeDays);
                }
            }
        }
        // 封装数据
        rainfallResult.setMaxRainDayList(maxRainDayList);
    }

    /**
     * 方法描述: 计算前期雨量指数
     *
     * @param allInformationMap 包含所有信息的Map结果集
     * @param rainfallStartDate 降雨起始日
     * @return 前期雨量指数
     * @author yanglichen
     * @date 2020-07-27 09:02
     **/
    private static Double getBeforeRainfallPoint(
            Map<String, Map<Date, RaPDayRtVo>> allInformationMap, Date rainfallStartDate) {
        //根据降雨起始日得到15天前的日期
        Date befor15DayDate = DateUtils.getNextDateByTimeZone(rainfallStartDate, 14);
        //处理时间
        befor15DayDate.setTime(befor15DayDate.getTime() - DateUtils.ONE_DAY_MILLI_SECOND);
        Date tempStartDate = new Date(rainfallStartDate.getTime() - DateUtils.ONE_DAY_MILLI_SECOND);
        //根据降雨日期和雨前日期得到一个日期列表
        List<Date> dateList =
                DateUtils.getDateListByStartDateAndEndDate(befor15DayDate, tempStartDate);
        //得到这15天的结果级别(外层List是日期分组,小日期在前)
        List<List<RaPDayRtVo>> raPDayRtVosList =
                getDateListByRaPDayRtVo(allInformationMap, dateList);
        //得到站码和最新权重的映射
        Map<String, Double> weightMap = getNewWeiight(allInformationMap);
        //得到15天的每日面平均雨量
        List<Double> dayRfList = getDayRainfallQ(raPDayRtVosList, weightMap);
        //得到每日的P值 = 面平均雨量 * 0.85 ^ 第几天就是第几次方
        List<Double> dayPList = getDayPList(dayRfList);
        //累加每日的P
        double sumP = 0D;
        for (Double dayP : dayPList) {
            sumP += dayP;
        }
        return sumP;
    }

    /**
     * 方法描述: 根据每日面雨量得到每日的P值并且分装到一个列表中
     *
     * @param dayRfList 每日面雨量列表
     * @return 每日的P
     * @author yanglichen
     * @date 2020-07-27 09:36
     **/
    private static List<Double> getDayPList(List<Double> dayRfList) {
        //得到每日的P值 = 面平均雨量 * (0.85 ^ 第几天就是第几次方)
        List<Double> dayPList = new ArrayList<>();
        //遍历每日面平均雨量列表(列表中的顺序是小日期在前,所以列表中的第一个元素实际上是第15天的数据)
        for (int i = 0; i < dayRfList.size(); i++) {
            double dayP = Math.pow(0.85, dayRfList.size() - i) * dayRfList.get(i);
            dayPList.add(dayP);
        }
        return dayPList;
    }

    /**
     * 方法描述: 得到每日面雨量列表
     *
     * @param raPDayRtVosList 按照日期分组的降雨列表
     * @return 每日面雨量列表
     * @author yanglichen
     * @date 2020-07-27 09:19
     **/
    private static List<Double> getDayRainfallQ(
            List<List<RaPDayRtVo>> raPDayRtVosList, Map<String, Double> newWeight) {
        List<Double> dayRfList = new ArrayList<>();
        /*面平均雨量 = 当天下所有站码的降雨 * 各自站码最新的权重系数*/
        for (List<RaPDayRtVo> raPDayRtVoList : raPDayRtVosList) {
            double dayRf = 0D;
            for (RaPDayRtVo raPDayRtVo : raPDayRtVoList) {
                String stcd = raPDayRtVo.getStcd();
                //得到权重
                double weight = newWeight.get(stcd);
                //所有站码的降雨 * 各自最新的权重
                dayRf += raPDayRtVo.getRf() * weight;
            }
            dayRfList.add(dayRf);
        }
        return dayRfList;
    }

    /**
     * 方法描述: 得到最大点雨量,和暴雨中心
     *
     * @param allInformationMap 包含所有信息的Map结果集
     * @param rainfallStartDate 降雨起始日
     * @param rainfallEndDate   降雨截止日
     * @return 封装有最大点雨量信息, 暴雨中心(站码)的Map
     * @author yanglichen
     * @date 2020-07-27 08:19
     **/
    private static MaxRainObjectVo getMaxRainfall(
            Map<String, Map<Date, RaPDayRtVo>> allInformationMap,
            Date rainfallStartDate, Date rainfallEndDate) {
        //构造结果集
        MaxRainObjectVo maxRainObjectVo = new MaxRainObjectVo();
        //得到时间轴对象
        List<Date> dateList =
                DateUtils.getDateListByStartDateAndEndDate(rainfallStartDate, rainfallEndDate);
        //得到按照站码分组的结果
        List<List<RaPDayRtVo>> raPDayRtVosList =
                getRaPDayRtVosListByStcdList(allInformationMap, dateList);
        //构造一个数组,数组长度等于站码的数量
        double[] maxRainfallQ = new double[raPDayRtVosList.size()];
        //遍历站码分组列表
        for (int i = 0; i < raPDayRtVosList.size(); i++) {
            double sumRf = 0;
            for (int j = 0; j < raPDayRtVosList.get(i).size(); j++) {
                sumRf += raPDayRtVosList.get(i).get(j).getRf();
            }
            maxRainfallQ[i] = sumRf;
        }
        //找到最大值对应的下标
        double maxRf = maxRainfallQ[0];
        int tempIndex = 0;
        for (int i = 0; i < maxRainfallQ.length; i++) {
            if (maxRf < maxRainfallQ[i]) {
                maxRf = maxRainfallQ[i];
                tempIndex = i;
            }
        }
        /*
        这个for循环结束的时候tempIndex就是最大值对应的下标
        因为这个是按照上面的List遍历累加得到的结果
        所以这个tempIndex对应的也是List中最大雨量站的索引
        */
        String maxRfStcd = "";
        String maxRfStnm = "";
        String hncd = "";
        String rvnm = "";
        Double lttd = null;
        Double lgtd = null;
        if (raPDayRtVosList.get(tempIndex).size() != 0) {
            maxRfStcd = raPDayRtVosList.get(tempIndex).get(0).getStcd();
            maxRfStnm = raPDayRtVosList.get(tempIndex).get(0).getStnm();
            hncd = raPDayRtVosList.get(tempIndex).get(0).getHncd();
            rvnm = raPDayRtVosList.get(tempIndex).get(0).getRvnm();
            lttd = raPDayRtVosList.get(tempIndex).get(0).getLttd();
            lgtd = raPDayRtVosList.get(tempIndex).get(0).getLgtd();
        }
        //封装信息
        maxRainObjectVo.setStcd(maxRfStcd);
        maxRainObjectVo.setStnm(maxRfStnm);
        maxRainObjectVo.setMaxRf(maxRf);
        // 经纬度
        maxRainObjectVo.setLttd(lttd);
        maxRainObjectVo.setLgtd(lgtd);
        // 水系
        maxRainObjectVo.setHncd(hncd);
        // 河流
        maxRainObjectVo.setRvnm(rvnm);

        return maxRainObjectVo;
    }

    /**
     * 方法描述: 得到时段雨量
     *
     * @param allInformationMap 所有信息的Map
     * @param rainfallStartDate 降雨起始日
     * @param rainfallEndDate   降雨结束的日期
     * @return 时段雨量信息
     * 日期从小到大(2020/7/1, 2020/7/2, 2020/7/3.......)
     * @author yanglichen
     * @date 2020-08-18 10:05
     **/
    private static List<Double> getTimesRainfallQ(
            Map<String, Map<Date, RaPDayRtVo>> allInformationMap, Date rainfallStartDate, Date rainfallEndDate) {
        //构造结果集
        List<Double> timesRainfallQ = new ArrayList<>();
        //得到时间轴对象
        List<Date> dateList =
                DateUtils.getDateListByStartDateAndEndDate(rainfallStartDate, rainfallEndDate);
        //得到按照时间分组的对象
        List<List<RaPDayRtVo>> raPDayRtVosList =
                getDateListByRaPDayRtVo(allInformationMap, dateList);
        //得到新的权重信息
        Map<String, Double> newWeightMap = getNewWeiight(allInformationMap);
        /*
         * 最新的计算逻辑
         * 2020/9/2
         * 时段雨量 = 每日(雨量站降雨数据*自己的新权重)
         */
        //遍历分组对象
        for (List<RaPDayRtVo> raPDayRtVoList : raPDayRtVosList) {
            //雨量
            double dayRainfallQ = 0D;
            //遍历每日的列表对象
            for (RaPDayRtVo raPDayRtVo : raPDayRtVoList) {
                //得到新的权重
                double newWeight = newWeightMap.get(raPDayRtVo.getStcd());
                //计算降雨的毫米数
                double rainfallQ = raPDayRtVo.getRf() * newWeight;
                //累加当日的各个雨量站的降雨毫米数
                dayRainfallQ += rainfallQ;
            }
            //每日降雨毫米数 = 累加后的当日的各个雨量站的降雨毫米数/总控制面积 放入列表中
            timesRainfallQ.add(dayRainfallQ);
        }
        return timesRainfallQ;
    }

    /**
     * 方法描述: 得到时段雨量笼罩面积
     *
     * @param allInformationMap 所有信息的Map
     * @param rainfallStartDate 降雨起始日
     * @param rainfallEndDate   降雨结束的日期
     * @param rainfallThreshold 用于统计的阈值
     * @return 时段雨量笼罩面积
     * 日期从小到大(2020/7/1, 2020/7/2, 2020/7/3.......)
     * @author yanglichen
     * @date 2020-07-24 16:50
     **/
    private static List<Double> getTimesRainfallArea(
            Map<String, Map<Date, RaPDayRtVo>> allInformationMap,
            Date rainfallStartDate, Date rainfallEndDate, Double rainfallThreshold) {
        //构造结果集
        List<Double> timesRainfallArea = new ArrayList<>();
        //得到时间轴对象
        List<Date> dateList =
                DateUtils.getDateListByStartDateAndEndDate(rainfallStartDate, rainfallEndDate);
        //得到按照时间分组的对象
        List<List<RaPDayRtVo>> raPDayRtVosList =
                getDateListByRaPDayRtVo(allInformationMap, dateList);
        //遍历分组对象
        for (List<RaPDayRtVo> raPDayRtVoList : raPDayRtVosList) {
            //日笼罩面积
            double dayRainfallArea = 0D;
            //遍历每日的列表对象
            for (RaPDayRtVo raPDayRtVo : raPDayRtVoList) {
                if (raPDayRtVo != null) {
                    //如果降雨超过了阈值才进行累加
                    Double dayRf = raPDayRtVo.getRf();
                    if (dayRf >= rainfallThreshold) {
                        dayRainfallArea += raPDayRtVo.getArea();
                    }
                }
            }
            timesRainfallArea.add(dayRainfallArea);
        }
        return timesRainfallArea;
    }

    /**
     * 方法描述: 根据时间轴得到一个以时间轴分组的雨量站列表对象
     *
     * @param allInformationMap 结果集大全Map
     * @param dateList          时间轴
     * @return 以时间轴分组的雨量站列表对象
     * @author yanglichen
     * @date 2020-07-24 16:55
     **/
    private static List<List<RaPDayRtVo>> getDateListByRaPDayRtVo(
            Map<String, Map<Date, RaPDayRtVo>> allInformationMap, List<Date> dateList) {
        List<List<RaPDayRtVo>> raPDayRtVosList = new ArrayList<>();
        //遍历时间轴对象
        for (Date date : dateList) {
            //构造二层结果集
            List<RaPDayRtVo> raPDayRtVoList = new ArrayList<>();
            //遍历外层Map
            for (Map.Entry<String, Map<Date, RaPDayRtVo>> firMap : allInformationMap.entrySet()) {
                if (firMap.getValue() != null) {
                    //根据第一层的value得到第二层的Map对象
                    Map<Date, RaPDayRtVo> secMap = firMap.getValue();
                    //根据日期得到对应的雨量站实例对象
                    if (secMap.get(date) != null) {
                        //得到的结果不为null,放入二级列表中
                        raPDayRtVoList.add(secMap.get(date));
                    }
                }
            }
            //二级列表放入一级列表中
            raPDayRtVosList.add(raPDayRtVoList);
        }
        return raPDayRtVosList;
    }

    /**
     * 方法描述: 得到总降雨量
     *
     * @param allInformationMap 所有信息的Map
     * @param rainfallStartDate 降雨起始日
     * @param rainfallEndDate   降雨结束的日期
     * @return 总降雨量
     * @author yanglichen
     * @date 2020-07-24 15:17
     **/
    private static Double getTotalRainfallQ(Map<String, Map<Date, RaPDayRtVo>> allInformationMap, Date rainfallStartDate, Date rainfallEndDate) {
        //得到时间轴对象
        List<Date> dateList =
                DateUtils.getDateListByStartDateAndEndDate(rainfallStartDate, rainfallEndDate);
        //得到按照站码列表分组的雨量站列表对象
        List<List<RaPDayRtVo>> raPDayRtVosList =
                getRaPDayRtVosListByStcdList(allInformationMap, dateList);
        //得到权重的映射表
        Map<String, Double> newWeightMap = getNewWeiight(allInformationMap);

        /*
         * 最新的计算逻辑
         * 2020/9/2
         * 总降雨量 = 累加(雨量站在降雨历时内总雨量*新的权重)
         */
        Double totalRainfallQ = 0D;
        for (List<RaPDayRtVo> raPDayRtVoList : raPDayRtVosList) {
            Double tempQ = 0D;
            //计算每个雨量站的降雨量
            if (raPDayRtVoList.size() != 0) {
                //得到控制面积
                String stcd = raPDayRtVoList.get(0).getStcd();
                //得到新的权重数据
                Double newWeight = newWeightMap.get(stcd);
                //遍历累加降雨量
                for (RaPDayRtVo raPDayRtVo : raPDayRtVoList) {
                    tempQ += raPDayRtVo.getRf();
                }
                //降雨量 * 控制面积
                tempQ = tempQ * newWeight;
            }
            totalRainfallQ += tempQ;
        }
        return totalRainfallQ;
    }

    /**
     * 方法描述: 计算得到新的权重系数,并将结果按照站码,新权重封装到一个Map中,后续操作取用数据
     *
     * @param allInformationMap 包含所有信息的Map结果集
     * @return 站码和新权重的映射Map
     * @author yanglichen
     * @date 2020-07-27 08:52
     **/
    private static Map<String, Double> getNewWeiight(Map<String, Map<Date, RaPDayRtVo>> allInformationMap) {
        //构造结果集
        Map<String, Double> newWeightMap = new HashMap<>();
        //得到每一个对象组成的实例
        List<RaPDayRtVo> raPDayRtVoList = getRaPDayRtVoList(allInformationMap);
        //遍历累加得到权重累加值
        double oldWeight = 0D;
        for (RaPDayRtVo raPDayRtVo : raPDayRtVoList) {
            oldWeight += raPDayRtVo.getWeight();
        }
        //遍历雨量站列表,按照比例进行计算新的权重
        for (RaPDayRtVo raPDayRtVo : raPDayRtVoList) {
            String stcd = raPDayRtVo.getStcd();
            double newWeight = raPDayRtVo.getWeight() / oldWeight;
            //放入Map中
            newWeightMap.put(stcd, newWeight);
        }
        return newWeightMap;
    }

    /**
     * 方法描述: 根据传入的时间轴从结果集中得到按照站码列表分组的雨量站列表对象
     *
     * @param allInformationMap 结果集大全Map
     * @param dateList          时间轴
     * @return 按照站码列表分组的雨量站列表对象
     * @author yanglichen
     * @date 2020-07-24 15:46
     **/
    private static List<List<RaPDayRtVo>> getRaPDayRtVosListByStcdList(
            Map<String, Map<Date, RaPDayRtVo>> allInformationMap, List<Date> dateList) {
        List<List<RaPDayRtVo>> raPDayRtVosList = new ArrayList<>();
        //遍历外层Map
        for (Map.Entry<String, Map<Date, RaPDayRtVo>> firMap : allInformationMap.entrySet()) {
            if (firMap.getValue() != null) {
                //根据第一层的value得到第二层的Map对象
                Map<Date, RaPDayRtVo> secMap = firMap.getValue();
                //构造结果中内层的List
                List<RaPDayRtVo> raPDayRtVoList = new ArrayList<>();
                //遍历时间轴对象
                for (Date date : dateList) {
                    //根据日期找到对象
                    RaPDayRtVo raPDayRtVo = secMap.get(date);
                    if (raPDayRtVo != null) {
                        //添加到二级列表内
                        raPDayRtVoList.add(raPDayRtVo);
                    }
                }
                //添加到一级列表中
                raPDayRtVosList.add(raPDayRtVoList);
            }
        }
        return raPDayRtVosList;
    }

    /**
     * 方法描述: 得到降雨总面积
     *
     * @param allInformationMap 所以信息的Map结果集
     * @return 降雨总面积
     * @author yanglichen
     * @date 2020-07-24 10:54
     **/
    private static Double getTotalRainfallArea(Map<String, Map<Date, RaPDayRtVo>> allInformationMap) {
        /*
         * 得到降雨的总面积
         *
         * 累加雨量站的实例的控制面积即可
         */
        List<RaPDayRtVo> raPDayRtVoList = getRaPDayRtVoList(allInformationMap);
        double totalArea = 0D;
        //累加控制面积
        for (RaPDayRtVo raPDayRtVo : raPDayRtVoList) {
            totalArea += raPDayRtVo.getArea();
        }
        return totalArea;
    }

    /**
     * 方法描述:
     * 从Map结果集中得到一个包含每个雨量站全部信息的一个实例组成的列表
     *
     * @param allInformationMap 所有信息的Map结果集
     * @return 包含每个雨量站全部信息的一个实例组成的列表
     * @author yanglichen
     * @date 2020-07-24 11:00
     **/
    private static List<RaPDayRtVo> getRaPDayRtVoList(Map<String, Map<Date, RaPDayRtVo>> allInformationMap) {
        List<RaPDayRtVo> raPDayRtVoList = new ArrayList<>();
        //遍历第一层Map
        for (Map.Entry<String, Map<Date, RaPDayRtVo>> firMap : allInformationMap.entrySet()) {
            if (firMap.getValue() != null) {
                //根据第一层的value得到第二层的Map
                Map<Date, RaPDayRtVo> secMap = firMap.getValue();
                //遍历第二层的Map
                for (Map.Entry<Date, RaPDayRtVo> nextMap : secMap.entrySet()) {
                    if (nextMap.getValue() != null) {
                        RaPDayRtVo raPDayRtVo = nextMap.getValue();
                        raPDayRtVoList.add(raPDayRtVo);
                        break;
                    }
                }
            }
        }
        return raPDayRtVoList;
    }

    /**
     * 方法描述: 计算降雨发生的开始时间
     *
     * @param rainfallDateCount 雨量站编码在所有时间点下的有降雨数据的计数项的Map
     * @param rainfallEndDate   降雨结束的时间
     * @param timeInterval      场次雨的间隔
     * @return 降雨开始的时间
     * @author yanglichen
     * @date 2022-05-09 21:00
     **/
    private static Date findRainfallStartDate(Map<Date, Integer> rainfallDateCount, Date rainfallEndDate, Integer timeInterval) {
        /*
        新的计算方法
        倒推数据
        直到找到有降雨的那一天
        这一天作为降雨截止日
        */
        while (rainfallDateCount.get(rainfallEndDate) == null) {
            rainfallEndDate.setTime(rainfallEndDate.getTime() - DateUtils.ONE_DAY_MILLI_SECOND);
        }
        //将上一场雨/计算的时间 赋值给一个用于内部处理的临时时间对象
        Date tempDate = new Date(rainfallEndDate.getTime());
        //初始化时间历时,用于判断场次雨的间隔
        int timesCount = 0;
        while (true) {
            //如果找不到这一天的数据
            if (rainfallDateCount.get(tempDate) == null) {
                //时间历时+1
                timesCount++;
            } else {
                //找到了重新设置为0
                timesCount = 0;
            }
            //如果时间历时>=跳出的历时范围
            if (timesCount >= timeInterval) {
                break;
            }
            //时间减一天
            tempDate.setTime(tempDate.getTime() - DateUtils.ONE_DAY_MILLI_SECOND);
        }
        return new Date(tempDate.getTime() + timeInterval * DateUtils.ONE_DAY_MILLI_SECOND);
    }

    /**
     * 方法描述: 寻找降雨结束的时间
     *
     * @param rainfallDateCount 雨量站编码在所有时间点下的有降雨数据的计数项的Map
     * @param rainfallEndDate   降雨结束的时间
     * @param timeInterval      场次雨的间隔
     * @return 降雨结束的时间
     * @author yanglichen
     * @date 2022-05-09 21:02
     **/
    private static Date findRainfallEndDate(Map<Date, Integer> rainfallDateCount, Date rainfallEndDate, Integer timeInterval) {
        long rainfallEndDateTime = rainfallEndDate.getTime();
        Date tempDate = new Date(rainfallEndDateTime);
        int timesCount = 0;
        while (true) {
            if (rainfallDateCount.get(tempDate) == null) {
                timesCount++;
            } else {
                timesCount = 0;
            }
            //日期增加一天
            if (timesCount > timeInterval) {
                break;
            }
            tempDate.setTime(tempDate.getTime() + DateUtils.ONE_DAY_MILLI_SECOND);
        }
        return new Date(tempDate.getTime() - timeInterval * DateUtils.ONE_DAY_MILLI_SECOND);
    }
}

